package com.fc.redap.shiro.filter;

import com.fc.redap.dtos.LoginRequest;
import com.fc.redap.dtos.LoginResponse;
import com.fc.redap.dtos.ResponseJson;
import com.fc.redap.shiro.realm.MyAuthenticationToken;
import com.google.gson.Gson;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import static com.fc.redap.utils.HttpContextUtils.isAjax;
import static com.fc.redap.utils.HttpContextUtils.sendJSON;

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
    private static final Logger log = LoggerFactory.getLogger(MyFormAuthenticationFilter.class);

    /**
     * 登录失败
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        if (!isAjax(request)) {
            setFailureAttribute(request, e);
            return true;
        }

        if (e instanceof UnknownAccountException) {
            sendJSON((HttpServletResponse) response, ResponseJson.error(404, "无效的账户"));
        } else if (e instanceof LockedAccountException) {
            sendJSON((HttpServletResponse) response, ResponseJson.error(405, "账号被锁定"));
        } else if (e instanceof IncorrectCredentialsException) {
            sendJSON((HttpServletResponse) response, ResponseJson.error(406, "密码错误"));
        } else if (e instanceof AuthenticationException) {
            sendJSON((HttpServletResponse) response, ResponseJson.error(500, "无效的账户或密码错误"));
        }

        return false;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue);
    }

    /**
     * 表示当访问拒绝时
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if(this.isLoginRequest(request, response)) {
            if(this.isLoginSubmission(request, response)) {
                if(log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }

                return this.executeLogin(request, response);
            } else {
                if(log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }

                return true;
            }
        } else {
            if(log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
            }

            if (!isAjax(request)) {
                this.saveRequestAndRedirectToLogin(request, response);
            } else {
                sendJSON((HttpServletResponse) response, ResponseJson.error(401, "您尚未登录或登录时间过长,请重新登录!"));
            }

            return false;
        }
    }

    /**
     * 登录成功
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        if (!isAjax(request)) {
            issueSuccessRedirect(request, response);
        } else {
            final LoginResponse loginResponse = new LoginResponse();
            loginResponse.setSid(subject.getSession().getId().toString());
            loginResponse.setCreateTime(subject.getSession().getStartTimestamp());
            ResponseJson responseJson = new ResponseJson();
            responseJson.setSuccess(true);
            responseJson.success(loginResponse);
            sendJSON((HttpServletResponse) response, responseJson);
        }
        return false;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        if(!isAjax(request)){
            String username = getUsername(request);
            String password = getPassword(request);
            return createToken(username, password, request, response);
        }else{
            try {
                String requestBody = IOUtils.toString(request.getReader());
                LoginRequest loginRequest = new Gson().fromJson(requestBody, LoginRequest.class);
                if(loginRequest.getUsername()!= null && loginRequest.getPhone()==null){
                    return createToken(loginRequest.getUsername(), loginRequest.getPassword(),loginRequest.getSmsCode(), "BY_USERNAME", request, response);
                }else if(loginRequest.getPhone() != null && loginRequest.getUsername() == null){
                    return createToken(loginRequest.getPhone(), loginRequest.getPassword(), loginRequest.getSmsCode(), "BY_PHONE", request, response);
                }else if(loginRequest.getPhone() != null && loginRequest.getSmsCode() != null){
                    return createToken(loginRequest.getPhone(), loginRequest.getPassword(),loginRequest.getSmsCode(), "BY_SMS_CODE", request, response);
                }else{
                    return createToken(loginRequest.getUsername(), loginRequest.getPassword(), loginRequest.getSmsCode(), "BY_USERNAME", request, response);
                }
            } catch (Exception e) {
                if(log.isTraceEnabled()) {
                    log.trace("Exception get response body from inputStream");
                }
                return createToken("", "", request, response);
            }
        }
    }

    protected AuthenticationToken createToken(String username, String password, String smsCode, String loginMethod,
                                              ServletRequest request, ServletResponse response) {
        boolean rememberMe = isRememberMe(request);
        String host = getHost(request);
        return new MyAuthenticationToken(username, password, rememberMe, host, loginMethod,smsCode);
    }
}
